#pragma once

#include "cmp.h"
#include "iter.h"
#include "num.h"
#include "option.h"
#include "ptr.h"
#include "tuple.h"

namespace sfc::slice {

using option::Option;
using tuple::Tuple;

template <class T>
struct Iter : iter::Iterator<Iter<T>, T&> {
  T* start;
  T* stop;

  [[sfc_inline]] Iter(T* start, T* stop) noexcept : start{start}, stop{stop} {}

  [[sfc_inline]] auto len() const -> usize {
    return usize(stop - start);
  }

  [[sfc_inline]] auto next() -> Option<T&> {
    if (start == stop) {
      return {};
    }
    return {*start++};
  }

  [[sfc_inline]] auto next_back() -> Option<T&> {
    if (start == stop) {
      return {};
    }
    return {*--stop};
  }
};

template <class T>
struct Slice {
  T* _ptr;
  usize _len;

  [[sfc_inline]] constexpr Slice() noexcept : _ptr{nullptr}, _len{0} {}

  [[sfc_inline]] constexpr Slice(T* ptr, usize len) noexcept : _ptr{ptr}, _len{len} {}

  template <usize N>
  [[sfc_inline]] constexpr Slice(T (&v)[N]) noexcept : _ptr{v}, _len{N} {}

  [[sfc_inline]] operator Slice<const T>() const {
    return {_ptr, _len};
  }

  [[sfc_inline]] auto ptr() -> T* {
    return _ptr;
  }

  [[sfc_inline]] auto len() const -> usize {
    return _len;
  }

  [[sfc_inline]] auto as_ptr() const -> const T* {
    return _ptr;
  }

  [[sfc_inline]] auto as_mut_ptr() -> T* {
    return _ptr;
  }

  [[sfc_inline]] auto is_null() const -> bool {
    return _ptr == nullptr;
  }

  [[sfc_inline]] auto is_empty() const -> bool {
    return _len == 0;
  }

  [[sfc_inline]] auto operator[](usize idx) const -> const T& {
    return _ptr[idx];
  }

  [[sfc_inline]] auto operator[](usize idx) -> T& {
    return _ptr[idx];
  }

  [[sfc_inline]] auto operator()(usize start, usize stop) const -> Slice<const T> {
    const auto a = cmp::min(_len, start);
    const auto b = cmp::min(_len, stop);
    const auto n = num::checked_sub(b, a);
    return {_ptr + a, n};
  }

  [[sfc_inline]] auto operator()(usize start, usize stop) -> Slice<T> {
    const auto a = cmp::min(_len, start);
    const auto b = cmp::min(_len, stop);
    const auto n = num::checked_sub(b, a);
    return {_ptr + a, n};
  }

  [[sfc_inline]] auto split_at(usize mid) const -> Tuple<Slice<const T>, Slice<const T>> {
    const auto idx = cmp::min(_len, mid);
    return {Slice<const T>{_ptr, idx}, Slice<const T>{_ptr + idx, _len - idx}};
  }

  [[sfc_inline]] auto split_at_mut(usize mid) -> Tuple<Slice, Slice> {
    const auto idx = cmp::min(_len, mid);
    return {Slice{_ptr, idx}, Slice{_ptr + idx, _len - idx}};
  }

  [[sfc_inline]] auto begin() const -> const T* {
    return _ptr;
  }

  [[sfc_inline]] auto end() const -> const T* {
    return _ptr + _len;
  }

  [[sfc_inline]] auto begin() -> T* {
    return _ptr;
  }

  [[sfc_inline]] auto end() -> T* {
    return _ptr + _len;
  }

  using Iter = slice::Iter<const T>;
  [[sfc_inline]] auto iter() const -> Iter {
    return Iter{_ptr, _ptr + _len};
  }

  using IterMut = slice::Iter<T>;
  [[sfc_inline]] auto iter_mut() -> IterMut {
    return IterMut{_ptr, _ptr + _len};
  }

  auto find(auto&& p) const -> Option<usize> {
    return this->iter().position(p);
  }

  auto rfind(auto&& p) const -> Option<usize> {
    return this->iter().rposition(p);
  }

  void format(auto& f) const {
    auto b = f.box("[,]");
    for (auto&& x : *this) {
      b.entry(x);
    }
  }
};

template <class T>
Slice(T*, usize) -> Slice<T>;

template <class T, usize N>
Slice(T (&)[N]) -> Slice<T>;

}  // namespace sfc::slice
